本节代码对应 GitHub 分支: chapter3

仓库传送门 (opens new window)

UI 层算是基本搞定了,不过之前都是用的 mock 数据,现在需要我们做数据层的开发了,一方面包括 Ajax 请求的处理,另一方面是 redux 的相关操作。

# axios 请求封装

首先安装:

npm install axios --save

大家先去 GitHub 网易云音乐接口 (opens new window) clone 这个 nodejs 项目然后运行在其他端口上,保证不和前端服务端口冲突。(在本项目 (opens new window)仓库的readme文档也有详细说明)

现在在 src/api 目录下新建 config.js 文件,里面编写 axios 的配置:

import axios from 'axios';

export const baseUrl = 'http://xxx自己填';

//axios 的实例及拦截器配置
const axiosInstance = axios.create ({
  baseURL: baseUrl
});

axiosInstance.interceptors.response.use (
  res => res.data,
  err => {
    console.log (err, "网络错误");
  }
);

export {
  axiosInstance
};

然后在同一个目录下新建 request.js 用来封装不同的网络请求,内容如下:

import { axiosInstance } from "./config";

export const getBannerRequest = () => {
  return axiosInstance.get ('/banner');
}

export const getRecommendListRequest = () => {
  return axiosInstance.get ('/personalized');
}

即需要的两个接口,到时候直接调这些函数即可。

# redux 层开发

在 Recommend 目录下,新建 store 文件夹,然后新建以下文件

actionCreators.js// 放不同 action 的地方
constants.js      // 常量集合,存放不同 action 的 type 值
index.js          // 用来导出 reducer,action
reducer.js        // 存放 initialState 和 reducer 函数

# 1. 声明初始化 state

初始化 state 在 reducer 中进行

//reducer.js
import * as actionTypes from './constants';
import { fromJS } from 'immutable';// 这里用到 fromJS 把 JS 数据结构转化成 immutable 数据结构

const defaultState = fromJS ({
  bannerList: [],
  recommendList: [],
});

# 2. 定义 constants

//constants.js
export const CHANGE_BANNER = 'recommend/CHANGE_BANNER';

export const CHANGE_RECOMMEND_LIST = 'recommend/RECOMMEND_LIST';

# 3. 定义 reducer 函数

在 reducer.js 文件中加入以下处理逻辑,由于存放的是 immutable 数据结构,所以必须用 set 方法来设置新状态,同时取状态用 get 方法。

export default (state = defaultState, action) => {
  switch (action.type) {
    case actionTypes.CHANGE_BANNER:
      return state.set ('bannerList', action.data);
    case actionTypes.CHANGE_RECOMMEND_LIST:
      return state.set ('recommendList', action.data);
    default:
      return state;
  }
}

# 4. 编写具体的 action

//actionCreators.js
import * as actionTypes from './constants';
import { fromJS } from 'immutable';// 将 JS 对象转换成 immutable 对象
import { getBannerRequest, getRecommendListRequest } from '../../../api/request';

export const changeBannerList = (data) => ({
  type: actionTypes.CHANGE_BANNER,
  data: fromJS (data)
});

export const changeRecommendList = (data) => ({
  type: actionTypes.CHANGE_RECOMMEND_LIST,
  data: fromJS (data)
});

export const getBannerList = () => {
  return (dispatch) => {
    getBannerRequest ().then (data => {
      dispatch (changeBannerList (data.banners));
    }).catch (() => {
      console.log ("轮播图数据传输错误");
    })
  }
};

export const getRecommendList = () => {
  return (dispatch) => {
    getRecommendListRequest ().then (data => {
      dispatch (changeRecommendList (data.result));
    }).catch (() => {
      console.log ("推荐歌单数据传输错误");
    });
  }
};

# 5. 将相关变量导出

//index.js
import reducer from './reducer'
import * as actionCreators from './actionCreators'

export { reducer, actionCreators };

如果以后要加入新状态,或者创建新的 reducer 模块,直接走这些步骤即可。

# 组件连接 Redux

首先,需要将 recommend 下的 reducer 注册到全局 store,在 store/reducer.js 中,内容如下:

import { combineReducers } from 'redux-immutable';
import { reducer as recommendReducer } from '../application/Recommend/store/index';

export default combineReducers ({
  recommend: recommendReducer,
});

注册完成!

现在在 Recommend/index.js 中,准备连接 Redux。组件代码更新如下:

import React, { useEffect } from 'react';
import Slider from '../../components/slider/';
import { connect } from "react-redux";
import * as actionTypes from './store/actionCreators';
import RecommendList from '../../components/list/';
import Scroll from '../../baseUI/scroll/index';
import { Content } from './style';

function Recommend (props){
  const { bannerList, recommendList } = props;

  const { getBannerDataDispatch, getRecommendListDataDispatch } = props;

  useEffect (() => {
    getBannerDataDispatch ();
    getRecommendListDataDispatch ();
    //eslint-disable-next-line
  }, []);

  const bannerListJS = bannerList ? bannerList.toJS () : [];
  const recommendListJS = recommendList ? recommendList.toJS () :[];

  return (
    <Content>
      <Scroll>
        <div>
          <Slider bannerList={bannerListJS}></Slider>
          <RecommendList recommendList={recommendListJS}></RecommendList>
        </div>
      </Scroll>
    </Content>
  );
}

// 映射 Redux 全局的 state 到组件的 props 上
const mapStateToProps = (state) => ({
  // 不要在这里将数据 toJS
  // 不然每次 diff 比对 props 的时候都是不一样的引用,还是导致不必要的重渲染,属于滥用 immutable
  bannerList: state.getIn (['recommend', 'bannerList']),
  recommendList: state.getIn (['recommend', 'recommendList']),
});
// 映射 dispatch 到 props 上
const mapDispatchToProps = (dispatch) => {
  return {
    getBannerDataDispatch () {
      dispatch (actionTypes.getBannerList ());
    },
    getRecommendListDataDispatch () {
      dispatch (actionTypes.getRecommendList ());
    },
  }
};

// 将 ui 组件包装成容器组件
export default connect (mapStateToProps, mapDispatchToProps)(React.memo (Recommend));

到这里,一个精美的推荐页面就开发完成了。

img

阅读全文